﻿using System;
using System.Runtime.Serialization;

namespace RayDen.Library.Core.Primitives {
    using System.ComponentModel;
    using System.Globalization;
    using System.Runtime.InteropServices;
 
    [TypeConverter(typeof(ExpandableObjectConverter))]
    [Serializable]
    [StructLayout(LayoutKind.Explicit)]
    [DataContract]
    [KnownType(typeof(VectorInt))]
    public struct VectorInt : IEquatable<VectorInt>, IVector {
        public static float VecEpsilon = 0.000001f;
        public static VectorInt Zero = new VectorInt(0);
        public static VectorInt UnitX = new VectorInt(1f, 0f, 0f);
        public static VectorInt UnitY = new VectorInt(0f, 1f, 0f);
        public static VectorInt UnitZ = new VectorInt(0f, 0f, 1f);


        [DataMember]
        [FieldOffset(0)]
        public float x;
        [FieldOffset(4)]
        [DataMember]
        public float y;
        [FieldOffset(8)]
        [DataMember]
        public float z;

        [FieldOffset(12)]
        [DataMember]
        public int a;

        public float X {
            get { return x; }
            set { x = value; }
        }

        public float Y {
            get { return y; }
            set { y = value; }
        }

        public float Z {
            get { return z; }
            set { z = value; }
        }
        public static readonly int SizeInBytes = 16;
        public float this[int index] {
            get {
                switch( index ) {
                    case 0:
                        return x;
                    case 1:
                        return y;
                    case 2:
                        return z;
                    default:
                        throw new ArgumentException("index");
                }
            }

            set {
                switch( index ) {
                    case 0:
                        x = value;
                        break;
                    case 1:
                        y = value;
                        break;
                    case 2:
                        z = value;
                        break;
                    default:
                        throw new ArgumentException("index");
                }
            }

        }

        public float Component( Axis p ) {
            switch( p ) {
                case Axis.PLANE_X:
                    return x;
                case Axis.PLANE_Y:
                    return y;
                case Axis.PLANE_Z:
                    return z;
                default:
                    throw new ArgumentException("index");
            }
        }


        public VectorInt( float a ) {
            x = y = z = a;
            this.a = 0;
            //     w = 0;
        }


        public VectorInt( float a, float b, float c ) {
            x = a;
            y = b;
            z = c;
            this.a = 0;

            // w = 0;
        }

        public VectorInt( ref VectorInt v ) {
            x = v.x;
            y = v.y;
            this.a = 0;
            z = v.z;
            //   w = 0;
        }
        public VectorInt( float[] val ) {
            x = val[0];
            y = val[1];
            z = val[2];
            this.a = 0;

            // w = 0;
        }
        public VectorInt( VectorInt v ) {
            x = v.x;
            y = v.y;
            z = v.z;
            this.a = 0;

            // w = 0;
        }

        public static unsafe float InvSqrt( float x ) {
            float xhalf = 0.5f * x;
            int i = *(int*) &x;
            i = 0x5f3759ff - (i >> 1);
            x = *(float*) &i;
            x = x * (1.5f - xhalf * x * x);
            return x;
        }

        public float Length {
            get {
                return MathLab.Sqrt(x * x + y * y + z * z);
            }
        }

        public float Length2() {
            return x * x + y * y + z * z;
        }

        public float InvLength {
            get {
                return InvSqrt(x * x + y * y + z * z);
            }
        }
        public VectorInt BCLNormalize() {
            float l = //Core(float)Math.InvSqrt(x * x + y * y + z * z);
                (1.0f / Length);
            x *= l;
            y *= l;
            z *= l;

            return this;
        }


        public VectorInt Normalize() {
            float l = 1f / MathLab.Sqrt(x * x + y * y + z * z);
            //InvSqrt(x * x + y * y + z * z);
            x *= l;
            y *= l;
            z *= l;

            return this;
        }



        public static float Clip( float vx, float min, float max ) {
            if( vx < min )
                vx = min;
            else if( vx > max )
                vx = max;
            return vx;

        }

        public void Modify( short axis, float value ) {
            switch( axis ) {
                case 0:
                    x = value;
                    break;
                case 1:
                    y = value;
                    break;
                case 2:
                    z = value;
                    break;
            }
        }

        public override string ToString() {
            return string.Format("{0:F4} {1:F4} {2:F4}", x, y, z);
        }
        public static VectorInt Clip( ref VectorInt v, float min, float max ) {
            if( v.x < min )
                v.x = min;
            else if( v.x > max )
                v.x = max;

            if( v.y < min )
                v.y = min;
            else if( v.y > max )
                v.y = max;

            if( v.z < min )
                v.z = min;
            else if( v.z > max )
                v.z = max;

            return v;
        }

        public override bool Equals( object obj ) {
            if( obj is VectorInt )
                return this.Equals((VectorInt) obj);

            return false;
        }

        public bool IsZero() {
            return (x == 0.0) && (y == 0.0) && (z == 0.0);
        }


        public override int GetHashCode() {
            return 0;
        }

        public VectorInt Each( Func<float, float> action ) {
            this.x = action(this.x);
            this.y = action(this.y);
            this.z = action(this.z);
            return this;
        }
        #region Operators

        public static float Dot( ref VectorInt a, ref VectorInt b ) //TODO TO STATIC CROSS & DOT ? Operators
        {
            return (a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static float AbsDot( ref VectorInt a, ref VectorInt b ) //TODO TO STATIC CROSS & DOT ? Operators
        {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        //public static float Dot( ref VectorInt a, ref Point b ) //TODO TO STATIC CROSS & DOT ? Operators
        //{
        //    return (a.x * b.x + a.y * b.y + a.z * b.z);
        //}

        public static VectorInt Cross( ref VectorInt a, ref VectorInt b ) {
            return
                new VectorInt( a.y * b.z - a.z * b.y, 
                            a.z * b.x - a.x * b.z, 
                            a.x * b.y - a.y * b.x);
        }
        //u.y*v.z-u.z*v.y, u.z*v.x-u.x*v.z, u.x*v.y-u.y*v.x
        public static void Cross( ref VectorInt a, ref VectorInt b, out VectorInt r ) {
            r.x = a.y * b.z - a.z * b.y;
            r.y = a.z * b.x - a.x * b.z;
            r.z = a.x * b.y - a.y * b.x;
            r.a = 0;

        }
        public static VectorInt Add( ref VectorInt a, ref VectorInt b ) {
            return new VectorInt(a.x + b.x, a.y + b.y, a.z + b.z);
        }

        public static VectorInt Sub( ref VectorInt a, ref VectorInt b ) {
            return new VectorInt(a.x - b.x, a.y - b.y, a.z - b.z);
        }

        public static float operator &( VectorInt v1, VectorInt v2 ) {
            return VectorInt.Dot(ref v1, ref  v2);
        }

        //public static float operator &( VectorInt v1, Point v2 ) {
        //    return VectorInt.Dot(ref v1, ref  v2);
        //}

        public static VectorInt operator ^( VectorInt a, VectorInt b ) {
            return VectorInt.Cross(ref a, ref b);
        }



        public static bool operator >( VectorInt a, VectorInt b ) {
            bool result = false;
            for( int i = 0; i < 3; i++ ) {
                result = result || (a[i] > b[i]);
            }
            return result;
        }

        public static bool operator <( VectorInt a, VectorInt b ) {
            bool result = false;
            for( int i = 0; i < 3; i++ ) {
                result = result || (a[i] < b[i]);
            }
            return result;
        }



        public static VectorInt operator +( VectorInt v1, VectorInt v2 ) {
            return VectorInt.Add(ref v1, ref v2);
        }

        public static VectorInt operator -( VectorInt v1, VectorInt v2 ) {
            return VectorInt.Sub(ref v1, ref v2);
        }

        public static VectorInt operator *( VectorInt v1, VectorInt v2 ) {
            return new VectorInt(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z);
        }

        public static VectorInt operator /( VectorInt v1, VectorInt v2 ) {
            return new VectorInt(v1.x / v2.x, v1.y / v2.y, v1.z / v2.z);
        }

        public static VectorInt operator -( VectorInt v ) {
            return new VectorInt(-v.x, -v.y, -v.z);
        }

        public static VectorInt operator +( VectorInt v, float f ) {
            return new VectorInt(v.x + f, v.y + f, v.z + f);
        }

        public static VectorInt operator -( VectorInt v, float f ) {
            return new VectorInt(v.x - f, v.y - f, v.z - f);
        }

        public static VectorInt operator *( VectorInt v, float f ) {
            return new VectorInt(v.x * f, v.y * f, v.z * f);
        }

        public static VectorInt operator *( float f, VectorInt v ) {
            return new VectorInt(v.x * f, v.y * f, v.z * f);
        }

        public static VectorInt operator /( VectorInt v, float f ) {
            float divider = 1.0f / f;
            return new VectorInt(v.x * divider, v.y * divider, v.z * divider);
        }

        public static VectorInt operator /( float f, VectorInt v ) {
            return new VectorInt(f / v.x, f / v.y, f / v.z);
        }

        public static bool operator ==( VectorInt a, VectorInt b ) {
            return a.Equals(b);
        }

        public static bool operator !=( VectorInt a, VectorInt b ) {
            if( (a.x != b.x) || (a.y != b.y) || (a.z != b.z) )
                return true;
            return false;
        }


        //public static VectorInt operator *(VectorInt VectorInt, Matrix matrix)
        //{
        //    return new VectorInt(VectorInt.X * matrix[0, 0] + VectorInt.Y * matrix[0, 1] + VectorInt.Z * matrix[0, 2] + matrix[0, 3],
        //        VectorInt.X * matrix[1, 0] + VectorInt.Y * matrix[1, 1] + VectorInt.Z * matrix[1, 2] + matrix[1, 3],
        //        VectorInt.X * matrix[2, 0] + VectorInt.Y * matrix[2, 1] + VectorInt.Z * matrix[2, 2] + matrix[2, 3]);
        //}


        #endregion

        public float Dot( ref VectorInt b ) {
            return (x * b.x + y * b.y + z * b.z);
        }

        public void Cross( ref VectorInt a ) {
            x = a.y * z - a.z * y;
            y = a.z * x - a.x * z;
            z = a.x * y - a.y * x;
        }

        #region Instance Operations

        public VectorInt Add( ref VectorInt a ) {
            this.x += a.x;
            this.y += a.y;
            this.z += a.z;

            return this;
        }

        public VectorInt Sub( ref VectorInt a ) {
            this.x -= a.x;
            this.y -= a.y;
            this.z -= a.z;

            return this;
        }

        public VectorInt Mul( ref VectorInt a ) {
            this.x *= a.x;
            this.y *= a.y;
            this.z *= a.z;

            return this;
        }

        public VectorInt Div( ref VectorInt a ) {
            this.x /= a.x;
            this.y /= a.y;
            this.z /= a.z;

            return this;
        }

        public VectorInt Add( float a ) {
            this.x += a;
            this.y += a;
            this.z += a;

            return this;
        }

        public VectorInt Sub( float a ) {
            this.x -= a;
            this.y -= a;
            this.z -= a;

            return this;
        }

        public VectorInt Mul( float a ) {
            this.x *= a;
            this.y *= a;
            this.z *= a;

            return this;
        }

        public VectorInt Div( float a ) {
            float d = 1 / a;

            this.x *= d;
            this.y *= d;
            this.z *= d;

            return this;
        }


        #endregion


        #region IEquatable<VectorInt> Members

        //UT !
        public bool Equals( VectorInt other ) {
            if( ((Object) other) == null )
                return false;
            float dx = (float) Math.Abs(other.x - x);
            float dy = (float) Math.Abs(other.y - y);
            float dz = (float) Math.Abs(other.z - z);

            return (dx < VecEpsilon) && (dy < VecEpsilon) && (dz < VecEpsilon);
        }

        #endregion

        #region Static Operator Realisation

        public static void Add( ref VectorInt a, ref VectorInt b, out VectorInt c ) {
            c.x = a.x + b.x;
            c.y = a.y + b.y;
            c.z = a.z + b.z;
            c.a = 0;
        }

        public static void Sub( ref VectorInt a, ref VectorInt b, out VectorInt c ) {
            c.x = a.x - b.x;
            c.y = a.y - b.y;
            c.z = a.z - b.z;
            c.a = 0;

        }

        public static void Mul( ref VectorInt a, ref VectorInt b, out VectorInt c ) {
            c.a = 0;

            c.x = a.x * b.x;
            c.y = a.y * b.y;
            c.z = a.z * b.z;
        }

        public static void Div( ref VectorInt a, ref VectorInt b, out VectorInt c ) {
            c.x = a.x - b.x;
            c.y = a.y - b.y;
            c.a = 0;
            c.z = a.z - b.z;
        }

        public static void Add( ref VectorInt a, float b, out VectorInt c ) {
            c.x = a.x + b;
            c.y = a.y + b;
            c.a = 0;
            c.z = a.z + b;
        }

        public static void Mul( ref VectorInt a, float b, out VectorInt c ) {
            c.x = a.x * b;
            c.a = 0;
            c.y = a.y * b;
            c.z = a.z * b;
        }

        public static void Sub( ref VectorInt a, float b, out VectorInt c ) {
            c.x = a.x - b;
            c.a = 0;
            c.y = a.y - b;
            c.z = a.z - b;
        }

        public static void Div( ref VectorInt a, float b, out VectorInt c ) {
            c.x = a.x / b;
            c.a = 0;
            c.y = a.y / b;
            c.z = a.z / b;
        }
        #endregion

        static readonly float Divider = 1.0f / 3.0f;

        public float Avg() {
            return (x + y + z) * Divider;
        }

        public float[] ToArray() {
            return new float[3] { x, y, z };
        }

        public VectorInt Abs() {
            x = (float) Math.Abs(x);
            y = (float) Math.Abs(y);
            z = (float) Math.Abs(z);

            return this;
        }
        public static VectorInt Normalize( VectorInt v ) {
            var x = v.x;
            var y = v.y;
            var z = v.z;
            float l = 1f / MathLab.Sqrt(x * x + y * y + z * z);
            return new VectorInt(x * l, y * l, z * l);
        }
        public static void CoordinateSystem( VectorInt v, out VectorInt v2, out VectorInt v3 ) {
            VectorInt v1 = v;

            if( (float) Math.Abs(v1.x) > (float) Math.Abs(v1.y) ) {
                float invLen = 1 / MathLab.Sqrt(v1.x * v1.x + v1.z * v1.z);
                v2 = new VectorInt(-v1.z * invLen, 0, v1.x * invLen);
            }
            else {
                float invLen = 1 / MathLab.Sqrt(v1.y * v1.y + v1.z * v1.z);
                v2 = new VectorInt(0, v1.z * invLen, -v1.y * invLen);
            }
            v3 = (v1 ^ v2);
        }
        public static void CoordinateSystem( ref VectorInt v, out VectorInt v2, out VectorInt v3 ) {
            VectorInt v1 = v;
            //v1.Normalize();

            if( (float) Math.Abs(v1.x) > (float) Math.Abs(v1.y) ) {
                float invLen = 1 / MathLab.Sqrt(v1.x * v1.x + v1.z * v1.z);
                v2 = new VectorInt(-v1.z * invLen, 0, v1.x * invLen);
            }
            else {
                float invLen = 1 / MathLab.Sqrt(v1.y * v1.y + v1.z * v1.z);
                v2 = new VectorInt(0, v1.z * invLen, -v1.y * invLen);
            }
            v3 = (v1 ^ v2);
        }

        public static void CoordinateSystem( ref Normal v, out VectorInt v2, out VectorInt v3 ) {
            var v1 = new VectorInt(v.ToArray());
            //v1.Normalize();

            if( (float) Math.Abs(v1.x) > (float) Math.Abs(v1.y) ) {
                float invLen = 1 / MathLab.Sqrt(v1.x * v1.x + v1.z * v1.z);
                v2 = new VectorInt(-v1.z * invLen, 0, v1.x * invLen);
            }
            else {
                float invLen = 1 / MathLab.Sqrt(v1.y * v1.y + v1.z * v1.z);
                v2 = new VectorInt(0, v1.z * invLen, -v1.y * invLen);
            }
            v3 = (v1 ^ v2);
        }

        public static VectorInt SphericalDirection( float sintheta, float costheta, float phi ) {
            return new VectorInt(sintheta * (float) Math.Cos(phi), sintheta * (float) Math.Sin(phi), costheta);
        }

        public static VectorInt SphericalDirection( float sintheta, float costheta, float phi,
                 ref VectorInt x, ref VectorInt y, ref  VectorInt z ) {
            return sintheta * (float) Math.Cos(phi) * x + sintheta * (float) Math.Sin(phi) * y +
                    costheta * z;
        }

        public static float SphericalTheta( ref VectorInt v ) {
            return (float) Math.Acos(Clip(v.z, -1, 1));
        }

        public static float SphericalPhi( ref VectorInt v ) {
            float p = (float) Math.Atan2(v.y, v.x);
            return (p < 0) ? p + 2f * (float) Math.PI : p;
        }

        public static float CosTheta( ref VectorInt w ) {
            return w.z;
        }

        public static float SinTheta( ref VectorInt w ) {
            return MathLab.Sqrt((float) Math.Max(0, 1 - w.z * w.z));
        }

        public static float SinTheta2( ref VectorInt w ) {
            return 1 - CosTheta(ref w) * CosTheta(ref w);
        }

        public static float CosPhi( ref VectorInt w ) {
            return w.x / SinTheta(ref w);
        }

        public static float SinPhi( ref VectorInt w ) {
            return w.y / SinTheta(ref w);
        }

        public static bool SameHemisphere( ref VectorInt w,
                ref VectorInt wp ) {
            return w.z * wp.z > 0;
        }
        public static float Dot( ref Normal n1, ref VectorInt v2 ) {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }
        public static float Dot( ref VectorInt v2, ref Normal n1 ) {
            return n1.x * v2.x + n1.y * v2.y + n1.z * v2.z;
        }

        public static float AbsDot( ref Normal a, ref VectorInt b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static float AbsDot( ref VectorInt a, ref  Normal b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }
        public static float AbsDot( ref Normal a, Normal b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }
        public static float AbsDot( ref Normal a, VectorInt b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }
        public static float AbsDot( VectorInt a, ref  Normal b ) {
            return Math.Abs(a.x * b.x + a.y * b.y + a.z * b.z);
        }

        public static VectorInt FromString( string v ) {
            var dta = v.Trim().Replace(",", ".").Split(' ');
            if( dta.Length != 3 ) {
                throw new ArgumentException("string is in incorrect format {0}", v);
            }
            return new VectorInt(
                    float.Parse(dta[0], CultureInfo.InvariantCulture),
                    float.Parse(dta[1], CultureInfo.InvariantCulture),
                    float.Parse(dta[2], CultureInfo.InvariantCulture)
                    );
        }

        public void SetComponent(byte axis, float value) {
            switch (axis) {
                case 0:
                    x = value;
                    break;
                case 1:
                    y = value;
                    break;
                case 2:
                    z = value;
                    break;
                default:
                    throw new ArgumentException("index");
            }
        }
    }

}